package org.urchr.beorganized.dao.tables;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.urchr.beorganized.dao.RandomGUID;

@Entity
@Table(name="notes")
//@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Note implements Serializable{
	
	private static final long serialVersionUID = -5955351986354253109L;
	
	public static final int SORT_MANUAL = 0;
	public static final int SORT_NAME = 1;
	public static final int SORT_CREATE_DATE = 2;
	public static final int SORT_EDIT_DATE = 3;
	
	public static final int SORT_TYPE_ASC = 0;
	public static final int SORT_TYPE_DESC = 1;
	
	public static final int TYPE_TEXT = 0;
	public static final int TYPE_IMAGE = 1;
	public static final int TYPE_AUDIO = 2;
	public static final int TYPE_TASK = 3;
	public static final int TYPE_EVENT = 4;
	public static final int TYPE_BMARK = 5;
	public static final int TYPE_FILE = 6;

	public static final int REC_TYPE_REPEAT = 0;
	public static final int REC_TYPE_AFTER = 1;
	
	public static final int REC_PERIOD_DAYS = 0;
	public static final int REC_PERIOD_WEEKS = 1;
	public static final int REC_PERIOD_MONTHS = 2;
	public static final int REC_PERIOD_YEARS = 3;
	
	private int id;
	private int left;
	private int right;
	private User user;
	private int level;
	private int type = TYPE_TEXT;
	private int sort = SORT_MANUAL;
	private int sortType = SORT_TYPE_ASC;
	private String name;
	private byte[] data;
	private Date createDate;
	private Date editDate;
	private Set<Tag> tags = new TreeSet<Tag>();
	private int locked;
	private String shareCode = "";
	private String recurrence = "";
	private Set<Star> stars = new HashSet<Star>();
	private Set<Permission> permissions = new HashSet<Permission>();
	private User editUser;
	private int maintenance = 0;
	private int customIcon = 0;
	private int archived = 0;
	
	private int priority = -1;
	private int completed = 0;
	
	private Date startDate = null;
	private Date endDate = null;
	
	private int fileSize = 0;
	private String fileName = null;
	private String contentType = null;
	private String url = null;
	
	//Transient
	private String textContent = null;
	private String datesInfo = null;
	private String shortDatesInfo = null;
	private int durationInMinutes = 0;
	private int urgentLevel = 0;
	private String tagsString = null;
	private String recurrenceInfo = null;
	private boolean recurred = false;
	private int recurrenceType = REC_TYPE_AFTER;
	private int recurrencePeriod = REC_PERIOD_DAYS;
	private int recurrenceDays = 1;
	
	public void setId(int id) {
		this.id = id;
	}
	
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	public int getId() {
		return id;
	}

	public void setLeft(int left) {
		this.left = left;
	}

	@Column(name="_left")
	public int getLeft() {
		return left;
	}

	public void setRight(int right) {
		this.right = right;
	}

	@Column(name="_right")
	public int getRight() {
		return right;
	}

	public void setUser(User user) {
		this.user = user;
	}

	@ManyToOne
	@JoinColumn(name="id_user")
	public User getUser() {
		return user;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	@Column
	public int getLevel() {
		return level;
	}

	public void setType(int type) {
		this.type = type;
	}

	@Column
	public int getType() {
		return type;
	}

	public void setSort(int sort) {
		this.sort = sort;
	}

	@Column
	public int getSort() {
		return sort;
	}

	public void setSortType(int sortType) {
		this.sortType = sortType;
	}

	@Column
	public int getSortType() {
		return sortType;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Column(columnDefinition="text")
	public String getName() {
		return name;
	}

	public void setData(byte[] data) {
		this.data = data;
	}

	@Lob
//	@Column(columnDefinition="longblob")
	public byte[] getData() {
		return data;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	@Temporal(TemporalType.TIMESTAMP)
	public Date getCreateDate() {
		return createDate;
	}

	public void setEditDate(Date editDate) {
		this.editDate = editDate;
	}

	@Temporal(TemporalType.TIMESTAMP)
	public Date getEditDate() {
		return editDate;
	}

	public void setTags(Set<Tag> tags) {
		this.tags = tags;
	}

	@OneToMany(targetEntity=Tag.class, mappedBy="note", cascade=CascadeType.ALL)
	public Set<Tag> getTags() {
		return tags;
	}
	
	@Transient
	public String getOrderBy(){
		String res = "";
		switch (sort) {
		case SORT_MANUAL:
			res = "n.left";
			break;
		case SORT_CREATE_DATE:
			res = "n.createDate";
			break;
		case SORT_EDIT_DATE:
			res = "n.editDate";
			break;
		case SORT_NAME:
			res = "n.name";
			break;
		}
		if(sortType==SORT_TYPE_DESC)
			res += " desc";
		return res;
	}

	public void setLocked(int locked) {
		this.locked = locked;
	}

	@Column
	public int getLocked() {
		return locked;
	}

	public void setShareCode(String shareCode) {
		this.shareCode = shareCode;
	}

	@Column
	public String getShareCode() {
		return shareCode;
	}

	public void setStars(Set<Star> stars) {
		this.stars = stars;
	}

	@OneToMany(targetEntity=Star.class, mappedBy="note", cascade=CascadeType.ALL)
	public Set<Star> getStars() {
		return stars;
	}

	public void setPermissions(Set<Permission> permissions) {
		this.permissions = permissions;
	}

	@OneToMany(targetEntity=Permission.class, mappedBy="note", cascade=CascadeType.ALL)
	public Set<Permission> getPermissions() {
		return permissions;
	}

	public void setEditUser(User editUser) {
		this.editUser = editUser;
	}

	@ManyToOne
	@JoinColumn(name="id_edit_user")
	public User getEditUser() {
		return editUser;
	}

	public void setMaintenance(int maintenance) {
		this.maintenance = maintenance;
	}

	@Column
	public int getMaintenance() {
		return maintenance;
	}

	public void setCustomIcon(int customIcon) {
		this.customIcon = customIcon;
	}

	@Column
	public int getCustomIcon() {
		return customIcon;
	}

	public void setPriority(int priority) {
		this.priority = priority;
	}

	@Column
	public int getPriority() {
		return priority;
	}

	public void initNote(User user){
		switch (getType()) {
			case TYPE_TEXT:
			case TYPE_EVENT:
			case TYPE_TASK:
			case TYPE_BMARK:
				if(data==null)
					textContent = "";
				else
					try {
						textContent = new String(data, "utf-8");
					} catch (UnsupportedEncodingException e) {
						textContent = "";
					}
			break;
		}
		SimpleDateFormat dateFormat = new SimpleDateFormat(user.getDateFormat());
		SimpleDateFormat timeFormat = new SimpleDateFormat(user.getTimeFormat());
		boolean acceptRecurrence = false;
		shortDatesInfo = "";
		switch (getType()) {
			case TYPE_TASK:
				if(startDate!=null){
					acceptRecurrence = true;
					datesInfo = dateFormat.format(startDate);
					if(completed==0){
						Calendar now = getUserNow(user);
						int c = compareDates(now.getTime(), startDate);
						if(c==0)
							urgentLevel = 1;
						if(c>0)
							urgentLevel = 2;
					}
				}
				break;
			case TYPE_EVENT:
				acceptRecurrence = true;
				durationInMinutes = (int) ((endDate.getTime()-startDate.getTime())/1000/60);
				Calendar start = Calendar.getInstance();
				start.setTime(startDate);
				start.add(start.MINUTE, user.getMinuteOffsetByDate(startDate));
				
				Calendar end = Calendar.getInstance();
				end.setTime(endDate);
				end.add(end.MINUTE, user.getMinuteOffsetByDate(endDate));
				
				Calendar now = getUserNow(user);
//				System.out.println("start.compareTo(now) =  "+start.compareTo(now)+
//						", end.compareTo(now) = "+end.compareTo(now));
				if(start.compareTo(now)<0 && end.compareTo(now)>0){
					urgentLevel = 2;
				}else{
					if(compareDates(start.getTime(), now.getTime())==0 && start.compareTo(now)>0)
						urgentLevel = 1;
				}
				datesInfo = dateFormat.format(start.getTime())+" "+timeFormat.format(start.getTime());
				shortDatesInfo = timeFormat.format(start.getTime());
				if(compareDates(start.getTime(), end.getTime())!=0){
					datesInfo += " - "+dateFormat.format(end.getTime())+" "+timeFormat.format(end.getTime());
					shortDatesInfo = dateFormat.format(start.getTime())+" "+timeFormat.format(start.getTime())+
							"-"+dateFormat.format(end.getTime())+" "+timeFormat.format(end.getTime());
				}else{
					if(!start.equals(end)){
						datesInfo += " - "+timeFormat.format(end.getTime());
						shortDatesInfo += "-"+timeFormat.format(end.getTime());
					}
				}
				break;
		}
		initRecurrence(acceptRecurrence);
	}
	
	public void initRecurrence(boolean acceptRecurrence){
		if(acceptRecurrence && !getRecurrence().equals("")){
			recurred = true;
			StringTokenizer st = new StringTokenizer(getRecurrence(), "_");
			String type = st.hasMoreTokens()?st.nextToken():"repeat";
			String days = st.hasMoreTokens()?st.nextToken():"1";
			String periodType = st.hasMoreTokens()?st.nextToken():"days";
			int d = 1;
			try {
				d = Integer.parseInt(days);
			} catch (Exception e) {
				// TODO: handle exception
			}
			setRecurrenceDays(d);
			if(type.equals("after")){
				setRecurrenceType(REC_TYPE_AFTER);
				recurrenceInfo = "repeat after ";				
				if(periodType.equals("days")){
					setRecurrencePeriod(REC_PERIOD_DAYS);
					recurrenceInfo += d==1?"1 day":(d+" days");
				}
				if(periodType.equals("weeks")){
					setRecurrencePeriod(REC_PERIOD_WEEKS);
					recurrenceInfo += d==1?"1 week":(d+" weeks");
				}
				if(periodType.equals("months")){
					setRecurrencePeriod(REC_PERIOD_MONTHS);
					recurrenceInfo += d==1?"1 month":(d+" months");
				}
				if(periodType.equals("years")){
					setRecurrencePeriod(REC_PERIOD_YEARS);
					recurrenceInfo += d==1?"1 year":(d+" years");
				}
			}else{
				setRecurrenceType(REC_TYPE_REPEAT);
				recurrenceInfo = "repeat ";
				if(periodType.equals("days")){
					setRecurrencePeriod(REC_PERIOD_DAYS);
					recurrenceInfo += d==1?"daily":("every "+d+" days");
				}
				if(periodType.equals("weeks")){
					setRecurrencePeriod(REC_PERIOD_WEEKS);
					recurrenceInfo += d==1?"weekly":("every "+d+" weeks");
				}
				if(periodType.equals("months")){
					setRecurrencePeriod(REC_PERIOD_MONTHS);
					recurrenceInfo += d==1?"monthly":("every "+d+" months");
				}
				if(periodType.equals("years")){
					setRecurrencePeriod(REC_PERIOD_YEARS);
					recurrenceInfo += d==1?"annually":("every "+d+" years");
				}
			}
		}else
			recurred = false;		
	}
	
	public void setArchived(int archived) {
		this.archived = archived;
	}

	public int getArchived() {
		return archived;
	}
	
	private static Random random = new Random();
	
	public static String genRandomString(){
//		return Long.toString(Math.abs(random.nextLong()), 36);
		return new RandomGUID().toWebString();
	}

	public void setCompleted(int completed) {
		this.completed = completed;
	}
	
	@Column
	public int getCompleted() {
		return completed;
	}

	public void setStartDate(Date startDate) {
		if(this.startDate!=null && startDate!=null){
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss Z");
//			Log.debug(this, "Changing date from "+dateFormat.format(this.startDate)+" to "+
//					dateFormat.format(startDate));
		}
		this.startDate = startDate;
	}

	@Temporal(TemporalType.TIMESTAMP)
	public Date getStartDate() {
		return startDate;
	}

	public void setEndDate(Date endDate) {
		this.endDate = endDate;
	}

	@Temporal(TemporalType.TIMESTAMP)
	public Date getEndDate() {
		return endDate;
	}

	public void setFileSize(int fileSize) {
		this.fileSize = fileSize;
	}

	@Column
	public int getFileSize() {
		return fileSize;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	@Column
	public String getFileName() {
		return fileName;
	}

	public void setTextContent(String textContent) {
		this.textContent = textContent;
	}

	@Transient
	public String getTextContent() {
		return textContent;
	}
	
	public static String prepareTextForView(String data, boolean replaceTags){
		String result = data;
		if(replaceTags)
			result = result.replace("<", "&lt;").replace(">", "&gt;");
		return result.replace("\n", "<br/>").replace("\r", "");
	}

	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

	@Column
	public String getContentType() {
		return contentType;
	}

	public void setDatesInfo(String datesInfo) {
		this.datesInfo = datesInfo;
	}

	@Transient
	public String getDatesInfo() {
		return datesInfo;
	}

	public void setUrgentLevel(int urgentLevel) {
		this.urgentLevel = urgentLevel;
	}

	@Transient
	public int getUrgentLevel() {
		return urgentLevel;
	}
	
	public static Calendar getGMTNow(){
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.MILLISECOND, -calendar.get(Calendar.ZONE_OFFSET));
		return calendar;
	}
	
	public static Calendar getUserNow(User user){
		Calendar calendar = getGMTNow();
		calendar.add(Calendar.MINUTE, user.getMinuteOffset());
		return calendar;
	}
	
	public static Calendar serverDateToGMT(Date date){
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.MILLISECOND, -calendar.get(Calendar.ZONE_OFFSET));
		return calendar;
	}
	
	public static int compareDates(Date d1, Date d2){
		return compareDates(d1, null, d2, null);
	}
	
	public static int compareDates(Date d1, User user1, Date d2, User user2){
		Calendar c1 = null;
		Calendar c2 = null;
		if(d1!=null){
			c1 = Calendar.getInstance();
			c1.setTime(d1);
		}
		if(d2!=null){
			c2 = Calendar.getInstance();
			c2.setTime(d2);
		}
		return compareDates(c1, user1, c2, user2);
	}
	
	public static int compareDates(Date d1, User user1, Calendar c2, User user2){
		Calendar c1 = null;
		if(d1!=null){
			c1 = Calendar.getInstance();
			c1.setTime(d1);
		}
		return compareDates(c1, user1, c2, user2);
	}
	
	public static int compareDates(Calendar c1, User user1, Date d2, User user2){
		Calendar c2 = null;
		if(d2!=null){
			c2 = Calendar.getInstance();
			c2.setTime(d2);
		}
		return compareDates(c1, user1, c2, user2);
	}
	
	public static int compareDates(Calendar c1, User user1, Calendar c2, User user2){
		if(c1==null && c2==null)
			return 0;
		if(c1==null && c2!=null)
			return 1;
		if(c1!=null && c2==null)
			return -1;
		if(null!=user1)
			c1.add(Calendar.MINUTE, user1.getMinuteOffsetByDate(c1.getTime()));
		if(null!=user2)
			c2.add(Calendar.MINUTE, user2.getMinuteOffsetByDate(c2.getTime()));
		if(c1.get(c1.YEAR)==c2.get(c2.YEAR) && c1.get(c1.DAY_OF_YEAR)==c2.get(c2.DAY_OF_YEAR)){
			return 0;
		}else
			if((c1.get(c1.YEAR)==c2.get(c2.YEAR) && c1.get(c1.DAY_OF_YEAR)<c2.get(c2.DAY_OF_YEAR))
					|| c1.get(c1.YEAR)<c2.get(c2.YEAR))
				return -1;
		return 1;
	}
	
	public static String dateToUserISO(Date date, User user, boolean fromGMT, boolean dateUnchanged){
		if(date==null)
			return null;
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
		if(dateUnchanged)
			return format.format(date);
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		if(!fromGMT)
			c.add(c.MILLISECOND, -c.get(c.ZONE_OFFSET));
		if(user!=null)
			c.add(c.MINUTE, user.getMinuteOffsetByDate(date));
		return format.format(c.getTime());
	}

	public void setUrl(String url) {
		this.url = url;
	}

	@Column
	public String getUrl() {
		return url;
	}

	public void setTagsString(String tagsString) {
		this.tagsString = tagsString;
	}

	@Transient
	public String getTagsString() {
		return tagsString;
	}

	public void setRecurrence(String recurrence) {
		this.recurrence = recurrence;
	}

	@Column
	public String getRecurrence() {
		return recurrence==null?"":recurrence;
	}

	public void setRecurrenceInfo(String recurrenceInfo) {
		this.recurrenceInfo = recurrenceInfo;
	}

	@Transient
	public String getRecurrenceInfo() {
		return recurrenceInfo;
	}

	public void setRecurred(boolean recurred) {
		this.recurred = recurred;
	}

	@Transient
	public boolean isRecurred() {
		return recurred;
	}

	public void setRecurrenceType(int recurrenceType) {
		this.recurrenceType = recurrenceType;
	}

	@Transient
	public int getRecurrenceType() {
		return recurrenceType;
	}

	public void setRecurrencePeriod(int recurrencePeriod) {
		this.recurrencePeriod = recurrencePeriod;
	}

	@Transient
	public int getRecurrencePeriod() {
		return recurrencePeriod;
	}

	public void setRecurrenceDays(int recurrenceDays) {
		this.recurrenceDays = recurrenceDays;
	}

	@Transient
	public int getRecurrenceDays() {
		return recurrenceDays<=0?1:recurrenceDays;
	}

	public void setShortDatesInfo(String shortDatesInfo) {
		this.shortDatesInfo = shortDatesInfo;
	}

	@Transient
	public String getShortDatesInfo() {
		return shortDatesInfo;
	}

	public void setDurationInMinutes(int durationInMinutes) {
		this.durationInMinutes = durationInMinutes;
	}

	@Transient
	public int getDurationInMinutes() {
		return durationInMinutes;
	}
}
